package ar.edu.itba.pod.legajo45129.node.api;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.log4j.Logger;

import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;
import ar.edu.itba.pod.legajo45129.balance.api.Balancer;
import ar.edu.itba.pod.legajo45129.manager.NodeManager;

public class ClusterAdministrationImpl implements ClusterAdministration {
	private static Logger logger = Logger
			.getLogger(ClusterAdministrationImpl.class);
	private String groupId;
	private Set<NodeInformation> nodes;

	public ClusterAdministrationImpl() throws RemoteException {
		UnicastRemoteObject.exportObject(this, 0);

		Registry registry;
		registry = LocateRegistry.createRegistry(NodeManager.getInstance()
				.getNodeInformation().port());

		logger.debug("Binding " + Node.CLUSTER_COMUNICATION);
		registry.rebind(Node.CLUSTER_COMUNICATION, this);

		this.nodes = new CopyOnWriteArraySet<NodeInformation>();
	}

	@Override
	public void createGroup() throws RemoteException {
		this.groupId = new String(Long.toString(System.currentTimeMillis()));
		logger.info("group created: " + this.groupId);
	}

	@Override
	public String getGroupId() throws RemoteException {
		return this.groupId;
	}

	@Override
	public boolean isConnectedToGroup() throws RemoteException {
		return this.groupId != null;
	}

	@Override
	public void connectToGroup(String host, int port) throws RemoteException,
			NotBoundException {
		logger.info("connecting to group through " + host + ":" + port);

		ClusterAdministration remoteClusterAdministration = NodeManager
				.getInstance().getClusterAdministration(host, port);
		if (!isConnectedToGroup()) {
			this.groupId = remoteClusterAdministration.getGroupId();
		} else {
			logger.warn("node already connected to " + this.groupId);
		}

		Set<NodeInformation> neighbors = remoteClusterAdministration
				.addNewNode(NodeManager.getInstance().getNodeInformation());
		for (NodeInformation v : neighbors) {
			nodes.add(v);
		}

		// prevent self added
		nodes.remove(NodeManager.getInstance().getNodeInformation());

		logger.info("group " + this.groupId + " connected");
		logger.info("start balancing...");
		
		NodeManager.getInstance().getAgentsBalancer().bullyElection(NodeManager.getInstance().getNodeInformation(), System.currentTimeMillis());
		logger.info("waiting for coordinator");
		//			Thread.sleep(WAIT_FOR_COORDINATOR);
		logger.info("the new coordinator is: " + NodeManager.getInstance().getCoordinator());
		
		Balancer.balance(NodeManager.getInstance().getCoordinator());
		
		logger.info("balancing finished");
	}

	@Override
	public void disconnectFromGroup(NodeInformation node)
			throws RemoteException, NotBoundException {
		logger.info("disconnecting from group " + this.groupId);
		
		if (node.equals(NodeManager.getInstance().getNodeInformation())) {
			for (NodeInformation n: nodes) {
				NodeManager.getInstance().getClusterAdministration(n.host(), n.port()).disconnectFromGroup(node);
			}
		}
		
		if (nodes.contains(node)) {
			nodes.remove(node);
			for (NodeInformation n: nodes) {
				NodeManager.getInstance().getClusterAdministration(n.host(), n.port()).disconnectFromGroup(node);
			}
		}
	}

	@Override
	public Set<NodeInformation> addNewNode(NodeInformation node)
			throws RemoteException, NotBoundException {
		logger.info("addNewNode: " + node);

		// add it back
		if (!this.nodes.contains(node) 
				&& !NodeManager.getInstance().getNodeInformation().equals(node)) {
			nodes.add(node);
			for (NodeInformation n : nodes) {
				NodeManager.getInstance()
						.getClusterAdministration(n.host(), n.port())
						.addNewNode(node);
			}

			NodeManager.getInstance()
					.getClusterAdministration(node.host(), node.port())
					.addNewNode(NodeManager.getInstance().getNodeInformation());
		}

		return this.nodes;
	}

	@Override
	public Set<NodeInformation> connectedNodes() throws RemoteException {
		Set<NodeInformation> ret = new HashSet<NodeInformation>(); 
		synchronized (nodes) {
			Iterator<NodeInformation> it = nodes.iterator();
			while(it.hasNext()){
				NodeInformation n = it.next();
				ret.add(n);
			}
			
		}
		return ret;
	}

}
